package com.crazyteam.genid.util;

import com.crazyteam.genid.constant.GenIdConstant;
import com.crazyteam.genid.exception.GenPrimaryKeyException;
import com.crazyteam.genid.exception.LoadPrimaryKeyException;
import com.crazyteam.genid.exception.UndeclaredPrimaryKeyException;
import com.crazyteam.genid.model.KeyInfo;
import com.crazyteam.genid.service.GenerateKeyService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Desc
 * @Author guoyang
 * @CreateTime 2016-3-3 下午1:15:02
 */
@Component("genPrimaryKeyFactory")
@Lazy(false)
public class GenPrimaryKeyFactory implements InitializingBean{
    
    private static final Logger log = LoggerFactory.getLogger(GenPrimaryKeyFactory.class);

    private ApplicationContext applicationContext;

    private static final Map<String, KeyInfo> keyMap = new HashMap<String, KeyInfo>();

    @Autowired
    private GenerateKeyService generateKeyService;

    private static GenerateKeyService generateKeyServiceHolder;

    // 编辑keyMapLock的同步锁
    private static final byte[] keyMapLock = new byte[0];

    
    /**
     * 获取next主键ID
     * 
     * @param primaryKeyName 主键名称
     * @return 主键值
     * @throws UndeclaredPrimaryKeyException 入参key值在数据库中未配置
     * @throws LoadPrimaryKeyException  从数据库中加载key值失败
     * @throws GenPrimaryKeyException  获取key值失败
     */
    public static long nextPrimaryKey(final String primaryKeyName) 
        throws UndeclaredPrimaryKeyException, LoadPrimaryKeyException, GenPrimaryKeyException {
        return nextPrimaryKey(primaryKeyName , GenIdConstant.INC_DEFAULT);
    }

    /**
     * 获取next主键ID
     * @param primaryKeyName 主键名称
     * @param inc 增加的步长
     * @return 主键值
     * @throws UndeclaredPrimaryKeyException 入参key值在数据库中未配置
     * @throws LoadPrimaryKeyException  从数据库中加载key值失败
     * @throws GenPrimaryKeyException  获取key值失败
     */
    public static long nextPrimaryKey(final String primaryKeyName, Integer inc) 
        throws UndeclaredPrimaryKeyException, LoadPrimaryKeyException, GenPrimaryKeyException{
        // 主键名称不能为空
        if (StringUtils.isBlank(primaryKeyName)) {
            throw new UndeclaredPrimaryKeyException("主键名称为空。primayKeyName is null");
        }

        // 通过主键名称获取对应的主键信息
        final KeyInfo keyInfo = loadKeyInfo(primaryKeyName);
        // 从主键信息中获取next主键
        return generateKeyServiceHolder.nextPrimaryKey(keyInfo, inc);
    }
    
    /**
     * 获取primayKeyName 对应的key信息
     */
    private static KeyInfo loadKeyInfo(final String primaryKeyName){
        // 首先从keyMap中取，如果取不到则从数据库中取
        KeyInfo keyInfo = keyMap.get(primaryKeyName);
        // 如果取不到，则从数据库中取
        if (keyInfo == null) {
            synchronized (keyMapLock) {
                keyInfo = keyMap.get(primaryKeyName);
                if (keyInfo == null) {
                    keyInfo = generateKeyServiceHolder.loadKeyInfo(primaryKeyName);
                    keyMap.put(primaryKeyName, keyInfo);
                }
            }
        }
        return keyInfo;
    }

    public static void init(List<String> initKeys){
        if(!CollectionUtils.isEmpty(initKeys)){
            for (String key : initKeys) {
                synchronized (keyMapLock) {
                    try {
                        KeyInfo keyInfo = generateKeyServiceHolder.loadKeyInfo(key);
                        keyMap.put(key, keyInfo);
                    } catch (Exception e) {
                        log.error("init key:" + key + " exception",e);
                    }
                }
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        generateKeyServiceHolder = generateKeyService;
    }
}
